package org.moonshot.chatty.parse;

import java.io.PrintStream;
import java.util.HashMap;

import org.moonshot.chatty.ChattyInternalError;

public class ChattyAST
{
    private final HashMap<Object, Object> children  = new HashMap<Object, Object> ();
    
    public final ASTType                   type;
    private int                            pushIndex = 0;
    
    public ChattyAST(ASTType aType)
    {
        type = aType;
    }
    
    /**
     * @param aKey
     * @param aValue
     * @throws ChattyInternalError
     */
    public void put (ASTKey aKey, Object aValue) throws ChattyInternalError
    {
        if( !aKey.clazz.isAssignableFrom (aValue.getClass ()) )
            throw new ChattyInternalError (new ClassCastException(), aKey + " does not accept "
                + aValue.getClass ());
        
        children.put (aKey, aValue);
    }
    
    /**
     * A type-checked retrieval mechanism. Returns the object from the given
     * index only if it is a subclass of the class given as a parameter; if
     * there is no object at the given index, or the types do not match, it
     * returns null.
     * @param <T> The type of the object returned
     * @param aIdx The index to retrieve from.
     * @param aClass The class of the type to return
     * @return
     */
    @SuppressWarnings ( { "unchecked" })
    protected <T> T get (ASTKey aKey, Class<T> clazz)
    {
        Object theValue = children.get (aKey);
        if( theValue == null || !clazz.isAssignableFrom (theValue.getClass ()) )
            return null;
        
        return (T) theValue;
    }
    
    /**
     * Put and get from integer indices >= 0
     * @param aIndex
     * @param aValue
     */
    public void put (int aIndex, Object aValue)
    {
        children.put (new Integer(aIndex), aValue);
    }
    
    /**
     * Put and get from integer indices >= 0
     * @param aIndex
     * @param aValue
     */
    public Object get (int aIndex)
    {
        return children.get (new Integer(aIndex));
    }
    
    /**
     * Put at an auto-generated integer index that increments with each push().
     * @param aValue
     */
    public void push (Object aValue)
    {
        put (pushIndex++, aValue);
    }
    
    public int size ()
    {
        return pushIndex;
    }
    
    public void printTo (PrintStream aOut)
    {
        printTo (aOut, 0);
    }
    
    private void printTo (PrintStream aOut, int aTabs)
    {
        aOut.print (type.toString ());
        aOut.print (" NODE");
        
        if( pushIndex > 0 )
        {
            aOut.println ();
            for(int i = 0; i < pushIndex; i++)
            {
                n(aOut, aTabs);
                aOut.print ('#');
                aOut.print (i);
                aOut.print (" = ");
                
                Object theValue = get(i);
                if( theValue instanceof ChattyAST )
                    ((ChattyAST)theValue).printTo (aOut, aTabs + 1);
                else if( theValue instanceof String )
                    aOut.println ("\"" + theValue + "\"");
                else
                    aOut.println (theValue.toString ());
            }
        }
        else
            aOut.println ();
        
        for(ASTKey theKey : ASTKey.values ())
        {
            Object theValue = get (theKey, Object.class);
            if( theValue != null )
            {
                n (aOut, aTabs);
                aOut.print ("* ");
                aOut.print (theKey.toString ());
                aOut.print (" = ");
                
                if( theValue instanceof ChattyAST )
                    ((ChattyAST) theValue).printTo (aOut, aTabs + 1);
                else if( theValue instanceof String )
                    aOut.println ("\"" + theValue + "\"");
                else
                    aOut.println (theValue.toString ());
            }
        }
    }
    
    private void n (PrintStream aOut, int aTabs)
    {
        for(int i = 0; i < aTabs; i++)
            aOut.print ("  ");
    }
}
